use std::{
    cmp::Ordering,
    fmt::format,
    net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6},
    str::FromStr,
    sync::Arc,
};

use anyhow::{Context, Result, anyhow};
use bytes::{Buf, BytesMut};
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    net::{TcpListener, TcpStream},
};

use super::ethan_proto::{EthanAuthResult, EthanCommandType, EthanRequestProto};
use crate::{
    proxy::{InOutBound, Proxy},
    proxy_config::ProxyConfig,
};

pub struct EthanSessionHandler {
    stream: TcpStream,
    buff: BytesMut,
    auth_data: Arc<Vec<u8>>,
}
impl EthanSessionHandler {
    pub fn new(stream: TcpStream, auth_data: Arc<Vec<u8>>) -> Self {
        EthanSessionHandler {
            stream,
            buff: BytesMut::with_capacity(1024 * 5),
            auth_data,
        }
    }
    pub async fn handle_request(&mut self) -> Result<()> {
        let request = self.read_request().await?;
        match request.cmd() {
            EthanCommandType::Auth => {
                let auth_result = match self.auth_with_client(request.data()).await {
                    true => EthanAuthResult::Success,
                    false => EthanAuthResult::IncorrectId,
                };
                let response = EthanRequestProto::new(
                    EthanCommandType::AuthResult,
                    [auth_result.into()].to_vec(),
                );
                self.stream.write_all(&response.into_bytes()).await?;
            }
            EthanCommandType::Trans => {}
            _ => {}
        }

        Ok(())
    }
    async fn read_request(&mut self) -> Result<EthanRequestProto> {
        EthanRequestProto::read_from_bytes(&mut self.buff)
    }
    async fn auth_with_client(&self, auth_data: &[u8]) -> bool {
        auth_data.cmp(&self.auth_data) == Ordering::Equal
    }
    async fn auth_request(&mut self) -> Result<EthanAuthResult> {
        let auth_data = EthanRequestProto::new(EthanCommandType::Auth, self.auth_data.to_vec());
        self.stream.write_all(&auth_data.into_bytes()).await?;

        todo!()
    }
}
pub async fn start_server(config: Arc<ProxyConfig>) -> Result<()> {
    let local_socket_addr =
        SocketAddrV4::from_str(format!("0.0.0.0:{}", config.in_bound.port).as_str())?;
    let tcp_listener = TcpListener::bind(local_socket_addr).await?;
    loop {
        if let Ok((tcpstream, _addr)) = tcp_listener.accept().await {
            let config = config.clone();
            tokio::spawn(async move {
                let mut session = EthanSessionHandler::new(tcpstream, config);
                match session.handle_request().await {
                    Ok(_) => {
                        log::trace!("handle success!");
                    }
                    Err(e) => {
                        log::error!("handle failed: {}", e);
                    }
                }
            });
        }
    }
}

pub enum InOutBoundSever {
    InBoundServer(InBoundServer),
    OutBoundServer(OutBoundServer),
}
impl From<InBoundServer> for InOutBoundSever {
    fn from(value: InBoundServer) -> Self {
        Self::InBoundServer(value)
    }
}
impl From<OutBoundServer> for InOutBoundSever {
    fn from(value: OutBoundServer) -> Self {
        Self::OutBoundServer(value)
    }
}
pub struct InBoundServer {
    listener: TcpListener,
    auth_data: Arc<Vec<u8>>,
}

impl InBoundServer {
    async fn start_listening(&self) -> Result<()> {
        loop {
            if let Ok((tcpstream, _addr)) = self.listener.accept().await {
                let auth_data=self.auth_data.clone();
                tokio::spawn(async move {
                  let mut session= EthanSessionHandler::new(tcpstream, auth_data);
                  session.handle_request();
                });
            }
        }
    }
}

type OutBoundServer = EthanSessionHandler;
pub struct EthanServer {
    config: Arc<ProxyConfig>,
    flag: InOutBound,
    server: InOutBoundSever,
}

impl Proxy for EthanServer {
    async fn start_serve(&mut self) -> Result<()> {
        todo!()
    }

    async fn prepare_serve(config: Arc<ProxyConfig>, flag: InOutBound) -> Result<Self>
    where
        Self: Sized,
    {
        if InOutBound::OutBound == flag {
            if let Some(server_ip) = config.out_bound.server
                && let Some(port) = config.out_bound.port
            {
                let addr = match server_ip {
                    IpAddr::V4(v4) => SocketAddr::V4(SocketAddrV4::new(v4, port)),
                    IpAddr::V6(v6) => SocketAddr::V6(SocketAddrV6::new(v6, port, 0, 0)),
                };
                let output_stream = TcpStream::connect(addr)
                    .await
                    .context(format!("can't connect to outbound addr: {}", addr))?;
                let mut session = EthanSessionHandler::new(
                    output_stream,
                    config
                        .out_bound
                        .ethan_id
                        .as_ref()
                        .unwrap()
                        .clone()
                        .as_bytes()
                        .to_vec()
                        .into(),
                );
                match session.auth_request().await {
                    Ok(auth_result) => match auth_result {
                        EthanAuthResult::Success => {
                            return Ok(Self {
                                config,
                                flag,
                                server: session.into(),
                            });
                        }
                        EthanAuthResult::ServerError => {
                            return Err(anyhow!(
                                "auth failed because of the remote server return failed! please goto remote server check detail info!"
                            ));
                        }
                        EthanAuthResult::IncorrectId => {
                            return Err(anyhow!("auth failed, the ethanId is not compaired!"));
                        }
                    },
                    Err(e) => {
                        return Err(e);
                    }
                }
            }
            return Err(anyhow!(
                "out bound section config server ip or port not incorrect!"
            ));
        } else {
            let addr = SocketAddr::from_str(format!("0.0.0.0:{}", config.in_bound.port).as_str())?;
            let tcplistener = TcpListener::bind(addr).await?;
            let inbound = InBoundServer {
                listener: tcplistener,
                auth_data: config
                    .in_bound
                    .ethan_id
                    .as_ref()
                    .unwrap()
                    .clone()
                    .as_bytes()
                    .to_vec()
                    .into(),
            };

            Ok(Self {
                config,
                flag,
                server: inbound.into(),
            })
        }
    }
}
